Since 2015, JavaScript has improved immensely.
It’s much more pleasant to use it now than ever.
In this article, we’ll look at JavaScript iterable objects.
Iterators that are Iterable
We can move the next
function into its own object method if we return this
in the Symbol.iterator
method.
For instance, we can write:
function iterate(...args) {
let index = 0;
const iterable = {
[Symbol.iterator]() {
return this;
},
next() {
if (index < args.length) {
return {
value: args[index++]
};
} else {
return {
done: true
};
}
},
};
return iterable;
}
We have the next
within the iterable
object we return.
The args
have the items that we want to iterate over.
We return the object with the value
while the index
is less than args.length
.
And we return an object with done
set to true
if there’s nothing left to iterate.
We return this
inside the Symbo.iterator
method so that the iteration can be done.
for-of loops work only with iterables and not the iterators directly.
Iterables always have the Symbol.iterator
method, so we got to put our iterator over our iterable object to make it iterable.
return()
and throw()
There’re 2 iterator methods that are optional.
return
gives us an iterator the opportunity to clean if iterator ends early.
throw
lets us forward a method call to a generator that’s iterated via yield*
.
Closing Iterators
We can use return
to close an iterator.
For instance, if we have the iterate
function that we have before.
Then we can call break
to end the for-of loop cleanly:
for (const x of iterate('foo', 'bar', 'baz')) {
console.log(x);
break;
}
return
must return an object.
This is because of how generators handle the return
statements.
Some constructors close iterators that aren’t completely clean up.
They include:
for-of
yield*
- destructuring
Array.from()
Map()
,Set()
,WeakMap()
,WeakSet()
Promise.all()
,Promise.race()
Combinators
Combinators are functions that combine existing iterables to create new ones.
For example, we can create one by writing:
function combinator(n, iterable) {
const iter = iterable[Symbol.iterator]();
return {
[Symbol.iterator]() {
return this;
},
next() {
if (0 < n) {
n--;
return iter.next();
} else {
return {
done: true
};
}
}
};
}
We create a function to return the first n
items from the iterable object.
Then we can use it by writing:
const arr = ['foo', 'bar', 'baz', 'qux'];
for (const x of combinator(3, arr)) {
console.log(x);
}
This will log the first 3 items of the arr
array.
Infinite Iterables
Iterable can return an infinite amount of values.
For instance, we can write:
function evenNums() {
let n = 0;
return {
[Symbol.iterator]() {
return this;
},
next() {
return {
value: (++n) * 2
};
}
}
}
to create an iterable object that returns even numbers.
Then we can create it by writing:
const nums = evenNums()
console.log(nums.next());
console.log(nums.next());
console.log(nums.next());
We call the evenNums
function to create iterator.
Then we call next
on each iterator to generate the numbers.
Conclusion
We can create iterable objects that return a finite and infinite number of values.